/*
 * File Name:               Math3D.cs
 * 
 * Description:             3D数学计算库
 *                          函数统一命名风格 以返回值为前缀 写清楚入参
 *                          并写出详细的注释
 *                          
 *                          距离 Distance
 *                          点 Point
 *                          方向 Direction
 *                          直线 Line
 *                          线段 Segment
 *                          平面 Plane
 *                          矩形区域 RectArea
 *                          三角区域 TriangleArea
 *                          是否 Is
 *                          角度 Angle
 *                          
 * Author:                  lisiyu <576603306@qq.com>
 * Create Date:             2017/01/19
 */

public class Math3D 
{
    #region 角度
    /// <summary>
    /// 两个向量之间的夹角
    /// 值域为 0到180 -180到0
    /// 顺时针为正
    /// </summary>
    /// <param name="from"></param>
    /// <param name="to"></param>
    /// <returns></returns>
    public static XNumber Angle_Direction2Direction(XVector3 from, XVector3 to, XVector3 normal)
    {
        var angle = XVector3.Angle(from, to);

        var cross = XVector3.Cross(from, to);
        if (XVector3.Dot(cross, normal) < 0)
            return -angle;

        return angle;
    }

    public static XNumber Angle_Direction2Direction(XVector3 from, XVector3 to)
    {
        XVector3 normal = XVector3.up;
        var angle = XVector3.Angle(from, to);

        var cross = XVector3.Cross(from, to);
        if (XVector3.Dot(cross, normal) < 0)
            return -angle;

        return angle;
    }
    #endregion 角度

    #region 距离
    /// <summary>
    /// 获取点到某个线段的距离
    /// </summary>
    /// <param name="position"></param>
    /// <param name="pos_01"></param>
    /// <param name="pos_02"></param>
    /// <returns></returns>
    public static XNumber Distance_Point2Segment(XVector3 position, XVector3 pos_01, XVector3 pos_02)
    {
        var dis1 = XVector3.Distance(position, pos_01);
        var dis2 = XVector3.Distance(position, pos_02);

        XNumber dot;
        if (dis1 <= dis2)
        {
            dot = XVector3.Dot(position - pos_01, pos_02 - pos_01);
        }
        else
        {
            dot = XVector3.Dot(position - pos_02, pos_01 - pos_02);
        }

        if (dot < 0)
        {

            return XIntMath.Min(dis1, dis2);
        }
        else
        {
            var dis3 = Distance_Point2Line(position, pos_01, pos_02);
            return dis3;
        }
    }

    /// <summary>
    /// 获取点到某个线段的距离 并返回最近的那个点
    /// </summary>
    /// <param name="position"></param>
    /// <param name="pos_01"></param>
    /// <param name="pos_02"></param>
    /// <returns></returns>
    public static XNumber Distance_Point2Segment(XVector3 position, XVector3 pos_01, XVector3 pos_02, out XVector3 nearestPos)
    {
        var dis1 = XVector3.Distance(position, pos_01);
        var dis2 = XVector3.Distance(position, pos_02);

        XNumber dot;
        if (dis1 <= dis2)
        {
            dot = XVector3.Dot(position - pos_01, pos_02 - pos_01);
        }
        else
        {
            dot = XVector3.Dot(position - pos_02, pos_01 - pos_02);
        }

        if (dot < 0)
        {
            if (dis1 <= dis2)
            {
                nearestPos = pos_01;
                return dis1;
            }
            else
            {
                nearestPos = pos_02;
                return dis2;
            }
        }
        else
        {
            var project = XVector3.Project(position - pos_01, XVector3.Cross(XVector3.Cross(position - pos_01, pos_01 - pos_02), pos_01 - pos_02));
            nearestPos = position - project;
            var dis3 = project.magnitude;
            return dis3;
        }
    }

    /// <summary>
    /// 获取点到某个直线的距离
    /// </summary>
    /// <param name="position"></param>
    /// <param name="pos_01"></param>
    /// <param name="pos_02"></param>
    /// <returns></returns>
    public static XNumber Distance_Point2Line(XVector3 position, XVector3 pos_01, XVector3 pos_02)
    {
        var ret = XVector3.Project(position - pos_01, XVector3.Cross(XVector3.Cross(position - pos_01, pos_01 - pos_02), pos_01 - pos_02)).magnitude;
        return ret;
    }

    /// <summary>
    /// 获取点到某个矩形区域的距离
    /// </summary>
    /// <param name="position"></param>
    /// <param name="pos01"></param>
    /// <param name="pos02"></param>
    /// <param name="pos03"></param>
    /// <param name="pos04"></param>
    /// <returns></returns>
    public static XNumber Distance_Point2RectArea(XVector3 position, XVector3 pos01, XVector3 pos02, XVector3 pos03, XVector3 pos04)
    {
        // 因为是对称的 所以可以取巧
        var centerPos = (pos01 + pos03) / 2;
        var normal = XVector3.Cross(pos01 - pos03, pos02 - pos04);

        var projectPos = centerPos + XVector3.ProjectOnPlane(position - centerPos, normal);

        // 平面范围内外判断
        var isInTriangle01 = Is_PointInTriangleArea(projectPos, pos01, pos02, pos03);
        var isInTriangle02 = Is_PointInTriangleArea(projectPos, pos03, pos04, pos01);
        if (isInTriangle01 || isInTriangle02)
        {
            // 距离平面的距离
            var len00 = XVector3.Project(position - centerPos, normal).magnitude;
            return len00;
        }
        else
        {
            // 距离边角线段的距离
            var len10 = Distance_Point2Segment(position, pos01, pos02);
            var len11 = Distance_Point2Segment(position, pos02, pos03);
            var len12 = Distance_Point2Segment(position, pos03, pos04);
            var len13 = Distance_Point2Segment(position, pos04, pos01);
            return XIntMath.Min(XIntMath.Min(len10, len11), XIntMath.Min(len12, len13));
        }
    }
    #endregion 距离

    #region 是否
    /// <summary>
    /// 判断点是否在一个三角形区域内
    /// </summary>
    /// <param name="position">点</param>
    /// <param name="posA"></param>
    /// <param name="posB"></param>
    /// <param name="posC"></param>
    /// <returns></returns>
    public static bool Is_PointInTriangleArea(XVector3 position, XVector3 posA, XVector3 posB, XVector3 posC)
    {
        return Is_DirectionInSameSide(posB - posA, posC - posA, position - posA)
            && Is_DirectionInSameSide(posC - posB, posA - posB, position - posB)
            && Is_DirectionInSameSide(posA - posC, posB - posC, position - posC);
    }

    /// <summary>
    /// 判断 dirA 和 dirB 是否在 direction 的同一边
    /// </summary>
    public static bool Is_DirectionInSameSide(XVector3 direction, XVector3 dirA, XVector3 dirB)
    {
        XVector3 v1 = XVector3.Cross(direction, dirA);
        XVector3 v2 = XVector3.Cross(direction, dirB);

        // 同边记为特殊情况 (这里是因为扑球容易判为同边而不去扑球了)
        if (v1 == XVector3.zero || v2 == XVector3.zero)
            return false;

        return XVector3.Dot(v1, v2) >= 0;
    }

    /// <summary>
    /// 判断 dirA 和 dirB 是否在 direction 的同一边 转到了同一个了平面再处理
    /// </summary>
    /// <returns></returns>
    public static bool Is_DirectionInSameSide_OnPlane(XVector3 direction, XVector3 dirA, XVector3 dirB)
    {
        return Is_DirectionInSameSide(XVector3.ProjectOnPlane(direction, XVector3.Cross(dirA, dirB)), dirA, dirB);
    }

    /// <summary>
    /// 判断 dirA 和 dirB 是否在 direction 的同一边 转到了同一个了平面再处理
    /// </summary>
    /// <returns></returns>
    public static bool Is_DirectionInSameSide_OnPlane(XVector3 direction, XVector3 dirA, XVector3 dirB, out XNumber angleA, out XNumber angleB)
    {
        var proj = XVector3.ProjectOnPlane(direction, XVector3.Cross(dirA, dirB));
        angleA = XVector3.Angle(proj, dirA);
        angleB = XVector3.Angle(proj, dirB);
        return Is_DirectionInSameSide(proj, dirA, dirB);
    }

    /// <summary>
    /// 判断 position 是否在一个立方体空间内
    /// </summary>
    /// <param name="position"></param>
    /// <param name="pos05">与 pos01对应的那个点</param>
    /// <returns></returns>
    public static bool Is_PointInCubeZone(XVector3 position, XVector3 pos01, XVector3 pos02, XVector3 pos03, XVector3 pos04, XVector3 pos05)
    {
        var offset = pos05 - pos01;
        var ret = Is_PointInRectZone(position, pos01, pos02, pos03, pos04);
        if (!ret)
            return false;

        ret = Is_PointInRectZone(position, pos01, pos02, pos02 + offset, pos05);
        if (!ret)
            return false;

        return true;
    }

    /// <summary>
    /// 判断 position 是否处在矩形区域的直线空间内
    /// </summary>
    /// <param name="position"></param>
    /// <param name="pos01"></param>
    /// <param name="pos02"></param>
    /// <param name="pos03"></param>
    /// <param name="pos04"></param>
    /// <returns></returns>
    public static bool Is_PointInRectZone(XVector3 position, XVector3 pos01, XVector3 pos02, XVector3 pos03, XVector3 pos04)
    {
        var ret = !Is_DirectionInSameSide_OnPlane(position - pos01, pos04 - pos01, pos02 - pos01);
        if (!ret)
            return false;
        ret = !Is_DirectionInSameSide_OnPlane(position - pos02, pos01 - pos02, pos03 - pos02);
        if (!ret)
            return false;
        ret = !Is_DirectionInSameSide_OnPlane(position - pos03, pos02 - pos03, pos04 - pos03);
        if (!ret)
            return false;
        ret = !Is_DirectionInSameSide_OnPlane(position - pos04, pos03 - pos04, pos01 - pos04);
        if (!ret)
            return false;
        return true;
    }

    /// <summary>
    /// 判断 Position 是否处在球体矩形椎体空间内
    /// </summary>
    /// <param name="position">需要判断的点</param>
    /// <param name="posRoot">锥形空间的root点</param>
    /// <param name="pos01">椎体的矩形截面上一点</param>
    /// <param name="pos02">椎体的矩形截面上一点</param>
    /// <param name="pos03">椎体的矩形截面上一点</param>
    /// <param name="pos04">椎体的矩形截面上一点</param>
    /// <returns></returns>
    public static bool Is_PointInSphericalZone(XVector3 position, XVector3 posRoot, XVector3 pos01, XVector3 pos02, XVector3 pos03, XVector3 pos04
        , out XNumber angle12_1, out XNumber angle12_2, out XNumber angle23_2, out XNumber angle23_3)
    {
        angle12_1 = 0;
        angle12_2 = 0;
        angle23_2 = 0;
        angle23_3 = 0;
        return !Is_DirectionInSameSide_OnPlane(position - posRoot, pos01 - posRoot, pos02 - posRoot, out angle12_1, out angle12_2)
            && !Is_DirectionInSameSide_OnPlane(position - posRoot, pos02 - posRoot, pos03 - posRoot, out angle23_2, out angle23_3);
    }
    #endregion 是否

    #region 点
    /// <summary>
    /// 求直线和平面的焦点
    /// </summary>
    /// <param name="linePosA">直线上点A</param>
    /// <param name="linePosB">直线上点B</param>
    /// <param name="planePosA">平面上点A</param>
    /// <param name="planeNormal">平面的法线</param>
    /// <returns></returns>
    public static XVector3 Point_Cross_lineAndPlane(XVector3 linePosA, XVector3 linePosB, XVector3 planePosA, XVector3 planeNormal)
    {
        var proA = XVector3.Dot((linePosA - planePosA), planeNormal);
        var proB = XVector3.Dot((linePosB - planePosA), planeNormal);

        if (proA * proB > 0)
        {
            // 在平面的同一侧
            var x = proA / proB;
            // AB不构成一条线无法计算
            if (x == 1)
                return XVector3.zero;
            return (linePosA - linePosB * x) / (1 - x);
        }
        else
        {
            // 在平面的两侧
            return XVector3.Lerp(linePosA, linePosB, proA / (proA + proB));
        }
    }

    /// <summary>
    /// 点在直线上的投影
    /// </summary>
    /// <param name="point"></param>
    /// <param name="linePosA"></param>
    /// <param name="linePosB"></param>
    /// <returns></returns>
    public static XVector3 Point_Project_Point2Line(XVector3 point, XVector3 linePosA, XVector3 linePosB)
    {
        var ret = point + XVector3.Project(linePosA - point, XVector3.Cross(XVector3.Cross(point - linePosA, linePosA - linePosB), linePosA - linePosB));
        return ret;
    }
    #endregion 点
}
